#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/printk.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/interrupt.h>
#include <linux/of.h>
#include <linux/clk.h>
#include <asm/memory.h>

static int major = -1;
static int last_minor = 0;

struct of_device_id id_table[] = {
    {.compatible = "zzh,fdt_dev"},
    {.compatible = "zzh,common"},
    {}
};

typedef struct
{
    struct device dev;
    struct cdev cdev;
    struct spinlock lock;

    char buf[128];
    void* __iomem pio;
    int irq;
    struct clk* clk;

}fdt_dev, * pfdt_dev;

static int hello_open(struct inode* i_node, struct file* f)
{
    fdt_dev* pfdt = container_of(i_node->i_cdev, fdt_dev, cdev);

    f->private_data = pfdt;
    // printk("bind pfdt: %s(%p) with file: %s(%p)\r\n", dev_name(&pfdt->dev), pfdt, f->f_path.dentry->d_iname, f);
    return 0;
}

static int hello_close(struct inode* i_node, struct file* f)
{
    return 0;
}

static ssize_t hello_read(struct file* f, char __user* data, size_t size, loff_t* loff)
{
    fdt_dev* pfdt = (fdt_dev*) f->private_data;

    if (size + *loff > sizeof(pfdt->buf))
        size = sizeof(pfdt->buf) - *loff;

    if (size <= 0)
        return -1;

    copy_to_user(data, pfdt->buf + *loff, size);
    *loff += size;
    return size;
}

static ssize_t hello_write(struct file* f, const char __user* data, size_t size, loff_t* loff)
{
    fdt_dev* pfdt = (fdt_dev*) f->private_data;

    if (size + *loff > sizeof(pfdt->buf))
        size = sizeof(pfdt->buf) - *loff;

    if (size <= 0)
        return -1;

    copy_from_user(pfdt->buf + *loff, data, size);
    *loff += size;

    return size;
}

struct file_operations hello_ops = {
    .open = hello_open,
    .release = hello_close,
    .read = hello_read,
    .write = hello_write,
};

static irqreturn_t irq_handler(int irq, void* id)
{
    return IRQ_HANDLED;
}

int fdt_probe(struct platform_device* pdev)
{
    fdt_dev* pfdt = NULL;
    dev_t num = 0;
    int ret = 0;
    printk(KERN_ALERT "fdt probe\r\n");

    if (major < 0) {
        ret = alloc_chrdev_region(&num, 0, 255, "fdt_cdev");
        if (ret != 0)
            goto err;
        major = MAJOR(num);
        last_minor = 0;
        printk("get major: %d\r\n", major);
    }

    pfdt = devm_kzalloc(&pdev->dev, sizeof(*pfdt), GFP_KERNEL);
    if (NULL == pfdt)
        goto err;

    device_initialize(&pfdt->dev);
    dev_set_name(&pfdt->dev, "fdtDev%d", last_minor);

    pfdt->dev.devt = MKDEV(major, last_minor++);

    platform_set_drvdata(pdev, pfdt);

    cdev_init(&pfdt->cdev, &hello_ops);
    spin_lock_init(&pfdt->lock);

    // pfdt->pio = devm_platform_ioremap_resource(pdev, 0);
    // if (IS_ERR(pfdt->pio))
    // {
    //     dev_err(&pdev->dev, "reg get error\r\n");
    //     goto err;
    // }
    // printk(KERN_INFO "get iomem area: %p\r\n", (pfdt->pio));

    // pfdt->irq = platform_get_irq_byname(pdev, "ready");
    // if (pfdt->irq < 0)
    // {
    //     dev_err(&pdev->dev, "irq get error\r\n");
    //     goto err;
    // }
    // printk(KERN_INFO "get irq num: %d\r\n", pfdt->irq);

    // pfdt->clk = devm_clk_get(&pdev->dev, "clock");
    // if (IS_ERR(pfdt->clk))
    // {
    //     dev_err(&pdev->dev, "clk get error\r\n");
    //     goto err;
    // }
    // printk(KERN_INFO "get clock: %p\r\n", (pfdt->clk));

    // ret = devm_request_irq(&pdev->dev, pfdt->irq, irq_handler, 0, dev_name(&pdev->dev), pfdt);

    // if (ret) {
    //     dev_err(&pdev->dev, "Can't request IRQ\n");
    // }

    cdev_device_add(&pfdt->cdev, &pfdt->dev);

    return 0;

err:
    printk("error occur\r\n");
    return -ENODEV;
}

int fdt_remove(struct platform_device* pdev)
{
    fdt_dev* pfdt = NULL;
    pfdt = (fdt_dev*) platform_get_drvdata(pdev);

    cdev_device_del(&pfdt->cdev, &pfdt->dev);
    return 0;
}

struct platform_driver plat_driver = {
    .probe = fdt_probe,
    .remove = fdt_remove,

    .driver = {
        .name = "fdt_dev",
        .of_match_table = id_table,
    },
};
module_platform_driver(plat_driver);

MODULE_DESCRIPTION("test module");
MODULE_AUTHOR("zzh");
MODULE_LICENSE("GPL");
